/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ch.pete.dailyscrumtimer;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

/**
 * Main activity of Daily Scrum Timer.
 */

public class DailyScrumTimerActivity extends Activity {
    /** One second in milli seconds. */
    private static final int ONE_SECOND = 1000;

    /** Used to covert the value to seconds. */
    private static final long CONVERT_TO_SECONDS = 1000;

    /** Used to execute the timer every second. */
    private Handler mIntervalHandler = new Handler();
    /** Executed every second to update the time. */
    private Runnable mIntervalTimer;
    /** The time in seconds until the timer expires. */
    private long mRemainingSeconds;
    /** The textfile where the timer value is displayed. */
    private TextView mTimerOutput;
    /** Instance of the helper class SoundGenerator to output the peep sound. */
    private SoundGenerator mSoundGenerator;

    /** The timer state in the beginning before the first start. */
    private static final int TIMER_STATE_BEFORE_FIRST_START = 0;
    /** The timer is running. */
    private static final int TIMER_STATE_STARTED = 1;
    /** The timer is stopped. */
    private static final int TIMER_STATE_STOPPED = 2;
    /** State of the timer. */
    private int mTimerState = TIMER_STATE_BEFORE_FIRST_START;

    /** Default value of mMaxSeconds. */
    private static final int MAX_SECONDS_DEFAULT = 120;
    /** Default value of mFirstBeep. */
    private static final int FIRST_BEEP_DEFAULT = 30;
    /** Default value of mSlowBeep. */
    private static final int SLOW_BEEP_DEFAULT = 20;
    /** Default value of mFastBeep. */
    private static final int FAST_BEEP_DEFAULT = 10;
    /** Default value of mDurationLastBeep. */
    private static final int DURATION_LAST_BEEP_DEFAULT = 10;

    /** The amount of seconds where the timer starts to count down. */
    private int mMaxSeconds;
    /** The amount of seconds left when the first beep is done. */
    private int mFirstBeep;
    /** The amount of seconds left when the slow beep starts. */
    private int mSlowBeep;
    /** The amount of seconds left when the fast beep starts. */
    private int mFastBeep;
    /** The duration of the last beep in seconds. */
    private int mDurationLastBeep;

    /** The length of the first beep. */
    private static final int FIRST_BEEP_TIME = 500;
    /** The length of the slow beeps. */
    private static final int SLOW_BEEP_TIME = 100;
    /** The length of the fast beeps. */
    private static final int FAST_BEEP_TIME = 100;
    /** The length of the vibrate alert. */
    private static final int VIBRATE_TIME = 3000;

    // statistics
    /** Total time of this Daily Scrum. */
    private int mTotalTime = 0;
    /** The amount of team members speaking at this Daily Scrum. */
    private int mTeamMembersCount = 0;

    @Override
    public final void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // use full screen and ensure, the screen is not switched of while the
        // timer is visible.
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD,
                WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

        mIntervalTimer = new IntervalTimer();
        synchronized (this) {
            mTimerOutput = (TextView) findViewById(R.id.timerOutput);
        }
        // the SoundGenerator is reinitialized in getPrefs()
        mSoundGenerator = new SoundGenerator(this, false);

        // Capture our button from layout
        Button buttonNext = (Button) this.findViewById(R.id.buttonNext);
        buttonNext.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nextClick();
            }
        });
        Button buttonFinished = (Button) this.findViewById(R.id.buttonFinished);
        buttonFinished.setVisibility(Button.INVISIBLE);
        buttonFinished.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                finishedClick();
            }
        });
    }

    @Override
    protected final void onStart() {
        super.onStart();
        getPrefs();
    }

    @Override
    public final void onWindowFocusChanged(final boolean hasFocus) {
        if (mTimerState != TIMER_STATE_BEFORE_FIRST_START) {
            if (hasFocus) {
                startTimer();
            } else {
                stopTimer();
            }
        }
        super.onWindowFocusChanged(hasFocus);
    }

    @Override
    protected void onDestroy() {
        mSoundGenerator.release();
        super.onDestroy();
    }

    // ---------------------------------------------------------------------------------------------
    // menu
    // ---------------------------------------------------------------------------------------------
    @Override
    public final boolean onCreateOptionsMenu(final Menu menu) {
        final MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public final boolean onPrepareOptionsMenu(final Menu menu) {
        // do not show the menu when the timer is running.
        if (mTimerState == TIMER_STATE_STARTED) {
            Toast.makeText(this, R.string.not_allowed_key, Toast.LENGTH_LONG).show();
            return false;
        } else {
            return super.onPrepareOptionsMenu(menu);
        }
    }

    @Override
    public final boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
            case R.id.preferences:
                Intent settingsActivity = new Intent(getBaseContext(),
                        Preferences.class);
                startActivity(settingsActivity);
                break;
            default:
                // do nothing
        }
        return true;
    }

    // ---------------------------------------------------------------------------------------------
    // preferences
    // ---------------------------------------------------------------------------------------------
    /**
     * Fetches the preferences and update the member variables.
     */
    private void getPrefs() {
        // Get the xml/preferences.xml preferences
        final SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(getBaseContext());
        final Editor editor = prefs.edit();

        final boolean increaseVolume = prefs.getBoolean("increase_volume", false);
        mSoundGenerator = new SoundGenerator(this, increaseVolume);

        try {
            mMaxSeconds = Integer
                    .parseInt(prefs.getString("timer_start", MAX_SECONDS_DEFAULT + ""));
            if (mMaxSeconds <= 0) {
                throw new NumberFormatException();
            }
        } catch (NumberFormatException e) {
            mMaxSeconds = MAX_SECONDS_DEFAULT;
            editor.putString("timer_start", mMaxSeconds + "");
        }
        try {
            mFirstBeep = Integer.parseInt(prefs.getString("timer_first_beep", FIRST_BEEP_DEFAULT
                    + ""));
        } catch (NumberFormatException e) {
            mFirstBeep = FIRST_BEEP_DEFAULT;
            editor.putString("timer_first_beep", mFirstBeep + "");
        }
        try {
            mSlowBeep = Integer.parseInt(prefs
                    .getString("timer_slow_beep", SLOW_BEEP_DEFAULT + ""));
        } catch (NumberFormatException e) {
            mSlowBeep = SLOW_BEEP_DEFAULT;
            editor.putString("timer_slow_beep", mSlowBeep + "");
        }
        try {
            mFastBeep = Integer.parseInt(prefs
                    .getString("timer_fast_beep", FAST_BEEP_DEFAULT + ""));
        } catch (NumberFormatException e) {
            mFastBeep = FAST_BEEP_DEFAULT;
            editor.putString("timer_fast_beep", mFastBeep + "");
        }
        try {
            mDurationLastBeep = Integer.parseInt(prefs
                    .getString("duration_last_beep", DURATION_LAST_BEEP_DEFAULT + ""));
        } catch (NumberFormatException e) {
            mDurationLastBeep = DURATION_LAST_BEEP_DEFAULT;
            editor.putString("duration_last_beep", mDurationLastBeep + "");
        }
        editor.commit();
    }

    // ---------------------------------------------------------------------------------------------
    // timer
    // ---------------------------------------------------------------------------------------------
    /**
     * Executed then the button "next" is clicked.
     */
    private void nextClick() {
        if (mTimerState == TIMER_STATE_STARTED) {
            mTotalTime += (mMaxSeconds - mRemainingSeconds);
            mTeamMembersCount++;
        }
        synchronized (this) {
            mSoundGenerator.stop();
            mRemainingSeconds = mMaxSeconds;
        }
        startTimer();
    }

    /**
     * Executed then the button "Scrum finished" is clicked.
     */
    private void finishedClick() {
        stopTimer();
        showSummary();
    }

    /**
     * Inner class to run the interval timer every second.
     */
    private class IntervalTimer implements Runnable {

        @Override
        public void run() {
            long remainingSecondsLocal;
            if (mRemainingSeconds > 0) {
                mRemainingSeconds--;
            }
            remainingSecondsLocal = mRemainingSeconds;
            updateDisplayTime();

            if (remainingSecondsLocal > 0) {
                if (remainingSecondsLocal <= mFastBeep) {
                    mSoundGenerator.playSound(FAST_BEEP_TIME);
                } else if (remainingSecondsLocal <= mSlowBeep
                        && remainingSecondsLocal % 2 == 0) {
                    mSoundGenerator.playSound(SLOW_BEEP_TIME);
                } else if (remainingSecondsLocal == mFirstBeep) {
                    mSoundGenerator.playSound(FIRST_BEEP_TIME);
                }
                mIntervalHandler.removeCallbacks(this);
                mIntervalHandler.postDelayed(this, ONE_SECOND);
            } else {
                vibrate(VIBRATE_TIME);
                mSoundGenerator.playSound(mDurationLastBeep * ONE_SECOND);

                // use maximum time for this member
                mTotalTime += (mMaxSeconds - mRemainingSeconds);
                mTeamMembersCount++;
                mTimerState = TIMER_STATE_STOPPED;
                mIntervalHandler.removeCallbacks(mIntervalTimer);
            }
        }
    }

    // ---------------------------------------------------------------------------------------------
    // Helper methods
    // ---------------------------------------------------------------------------------------------
    /**
     * Activate the timer.
     */
    private void startTimer() {
        if (mTimerState != TIMER_STATE_STARTED) {
            mTimerState = TIMER_STATE_STARTED;
            this.findViewById(R.id.buttonFinished).setVisibility(Button.VISIBLE);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                    WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            mIntervalHandler.removeCallbacks(mIntervalTimer);
            mIntervalHandler.postDelayed(mIntervalTimer, ONE_SECOND);
        }
        updateDisplayTime();
    }

    /**
     * Deactivate the timer.
     */
    private void stopTimer() {
        if (mTimerState == TIMER_STATE_STARTED) {
            mTimerState = TIMER_STATE_STOPPED;
            mIntervalHandler.removeCallbacks(mIntervalTimer);
            this.findViewById(R.id.buttonFinished).setVisibility(Button.INVISIBLE);
            updateDisplayTime();
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
    }

    /**
     * Shows the summary dialog box.
     */
    private void showSummary() {
        // calculate the values but do not update the member variables
        // to allow the timer to continue.
        long totalTime = mTotalTime + (mMaxSeconds - mRemainingSeconds);
        long teamMembers = mTeamMembersCount + 1;

        final StringBuffer message = new StringBuffer();
        final SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));
        message.append("Team members: " + teamMembers + "\n");
        message.append("Average: "
                + sdf.format(new Date(totalTime / teamMembers * CONVERT_TO_SECONDS))
                + "\n");
        message.append("Total: " + sdf.format(new Date(totalTime * CONVERT_TO_SECONDS)) + "");

        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(message).setCancelable(true)
                .setPositiveButton("Continue", new DialogInterface.OnClickListener() {
                    public void onClick(final DialogInterface dialog, final int id) {
                        // check if last person talked too long
                        if (mRemainingSeconds == 0) {
                            // simulate next click
                            nextClick();
                        } else {
                            startTimer();
                        }
                    }
                }).setNegativeButton("Finish", new DialogInterface.OnClickListener() {
                    public void onClick(final DialogInterface dialog, final int id) {
                        DailyScrumTimerActivity.this.finish();
                    }
                });
        final AlertDialog alert = builder.create();
        alert.show();
    }

    /**
     * Update the time on the display.
     */
    private void updateDisplayTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));
        synchronized (this) {
            mTimerOutput.setText(sdf.format(new Date(mRemainingSeconds * CONVERT_TO_SECONDS)));
        }
    }

    /**
     * Vibrate if allowed.
     * 
     * @param millis milli seconds to vibrate
     */
    private void vibrate(final int millis) {
        final Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        try {
            vibrator.vibrate(millis); // ms
        } catch (SecurityException e) {
            // permission denied, ignore vibration
            return;
        }
    }

}
